<template>
	<div class="window-container" :class="{ 'window-mobile': isDevice }">
		<form v-if="addNewRoom" @submit.prevent="createRoom">
			<input v-model="addRoomUsername" type="text" placeholder="Add username" />
			<button type="submit" :disabled="disableForm || !addRoomUsername">
				Create Room
			</button>
			<button class="button-cancel" @click="addNewRoom = false">
				Cancel
			</button>
		</form>

		<form v-if="inviteRoomId" @submit.prevent="addRoomUser">
			<input v-model="invitedUsername" type="text" placeholder="Add username" />
			<button type="submit" :disabled="disableForm || !invitedUsername">
				Add User
			</button>
			<button class="button-cancel" @click="inviteRoomId = null">
				Cancel
			</button>
		</form>

		<form v-if="removeRoomId" @submit.prevent="deleteRoomUser">
			<select v-model="removeUserId">
				<option default value="">
					Select User
				</option>
				<option v-for="user in removeUsers" :key="user._id" :value="user._id">
					{{ user.username }}
				</option>
			</select>
			<button type="submit" :disabled="disableForm || !removeUserId">
				Remove User
			</button>
			<button class="button-cancel" @click="removeRoomId = null">
				Cancel
			</button>
		</form>

		<chat-window
			:height="screenHeight"
			:theme="theme"
			:styles="styles"
			:current-user-id="currentUserId"
			:room-id="roomId"
			:rooms="loadedRooms"
			:loading-rooms="loadingRooms"
			:messages="messages"
			:messages-loaded="messagesLoaded"
			:rooms-loaded="roomsLoaded"
			:room-actions="roomActions"
			:menu-actions="menuActions"
			:room-message="roomMessage"
			:templates-text="templatesText"
			@fetch-more-rooms="fetchMoreRooms"
			@fetch-messages="fetchMessages"
			@send-message="sendMessage"
			@edit-message="editMessage"
			@delete-message="deleteMessage"
			@open-file="openFile"
			@open-user-tag="openUserTag"
			@add-room="addRoom"
			@room-action-handler="menuActionHandler"
			@menu-action-handler="menuActionHandler"
			@send-message-reaction="sendMessageReaction"
			@typing-message="typingMessage"
			@toggle-rooms-list="$emit('show-demo-options', $event.opened)"
		>
			<!-- <template #room-header="{ room }">
				{{ room.roomName }}
			</template> -->
		</chat-window>
	</div>
</template>

<script>
import * as firestoreService from '@/database/firestore'
import * as firebaseService from '@/database/firebase'
import * as storageService from '@/database/storage'
import { parseTimestamp, formatTimestamp } from '@/utils/dates'

import ChatWindow from './../../src/lib/ChatWindow'
// import ChatWindow, { Rooms } from 'vue-advanced-chat'
// import ChatWindow from 'vue-advanced-chat'
// import 'vue-advanced-chat/dist/vue-advanced-chat.css'
// import ChatWindow from './../../dist/vue-advanced-chat.umd.min.js'

export default {
	components: {
		ChatWindow
	},

	props: {
		currentUserId: { type: String, required: true },
		theme: { type: String, required: true },
		isDevice: { type: Boolean, required: true }
	},

	emits: ['show-demo-options'],

	data() {
		return {
			roomsPerPage: 15,
			rooms: [],
			roomId: '',
			startRooms: null,
			endRooms: null,
			roomsLoaded: false,
			loadingRooms: true,
			allUsers: [],
			loadingLastMessageByRoom: 0,
			roomsLoadedCount: false,
			selectedRoom: null,
			messagesPerPage: 20,
			messages: [],
			messagesLoaded: false,
			roomMessage: '',
			lastLoadedMessage: null,
			previousLastLoadedMessage: null,
			roomsListeners: [],
			listeners: [],
			typingMessageCache: '',
			disableForm: false,
			addNewRoom: null,
			addRoomUsername: '',
			inviteRoomId: null,
			invitedUsername: '',
			removeRoomId: null,
			removeUserId: '',
			removeUsers: [],
			roomActions: [
				{ name: 'inviteUser', title: 'Invite User' },
				{ name: 'removeUser', title: 'Remove User' },
				{ name: 'deleteRoom', title: 'Delete Room' }
			],
			menuActions: [
				{ name: 'inviteUser', title: 'Invite User' },
				{ name: 'removeUser', title: 'Remove User' },
				{ name: 'deleteRoom', title: 'Delete Room' }
			],
			styles: { container: { borderRadius: '4px' } },
			templatesText: [
				{
					tag: 'help',
					text: 'This is the help'
				},
				{
					tag: 'action',
					text: 'This is the action'
				},
				{
					tag: 'action 2',
					text: 'This is the second action'
				}
			]
			// ,dbRequestCount: 0
		}
	},

	computed: {
		loadedRooms() {
			return this.rooms.slice(0, this.roomsLoadedCount)
		},
		screenHeight() {
			return this.isDevice ? window.innerHeight + 'px' : 'calc(100vh - 80px)'
		}
	},

	mounted() {
		this.fetchRooms()
		firebaseService.updateUserOnlineStatus(this.currentUserId)
	},

	methods: {
		resetRooms() {
			this.loadingRooms = true
			this.loadingLastMessageByRoom = 0
			this.roomsLoadedCount = 0
			this.rooms = []
			this.roomsLoaded = true
			this.startRooms = null
			this.endRooms = null
			this.roomsListeners.forEach(listener => listener())
			this.roomsListeners = []
			this.resetMessages()
		},

		resetMessages() {
			this.messages = []
			this.messagesLoaded = false
			this.lastLoadedMessage = null
			this.previousLastLoadedMessage = null
			this.listeners.forEach(listener => listener())
			this.listeners = []
		},

		fetchRooms() {
			this.resetRooms()
			this.fetchMoreRooms()
		},

		async fetchMoreRooms() {
			if (this.endRooms && !this.startRooms) {
				this.roomsLoaded = true
				return
			}

			const query = firestoreService.roomsQuery(
				this.currentUserId,
				this.roomsPerPage,
				this.startRooms
			)

			const rooms = await firestoreService.getRooms(query)
			// this.incrementDbCounter('Fetch Rooms', rooms.size)

			this.roomsLoaded = rooms.empty || rooms.size < this.roomsPerPage

			if (this.startRooms) this.endRooms = this.startRooms
			this.startRooms = rooms.docs[rooms.docs.length - 1]

			const roomUserIds = []
			rooms.forEach(room => {
				room.data().users.forEach(userId => {
					const foundUser = this.allUsers.find(user => user?._id === userId)
					if (!foundUser && roomUserIds.indexOf(userId) === -1) {
						roomUserIds.push(userId)
					}
				})
			})

			// this.incrementDbCounter('Fetch Room Users', roomUserIds.length)
			const rawUsers = []
			roomUserIds.forEach(userId => {
				const promise = firestoreService
					.getUser(userId)
					.then(user => user.data())
				rawUsers.push(promise)
			})

			this.allUsers = [...this.allUsers, ...(await Promise.all(rawUsers))]

			const roomList = {}
			rooms.forEach(room => {
				roomList[room.id] = { ...room.data(), users: [] }

				room.data().users.forEach(userId => {
					const foundUser = this.allUsers.find(user => user?._id === userId)
					if (foundUser) roomList[room.id].users.push(foundUser)
				})
			})

			const formattedRooms = []

			Object.keys(roomList).forEach(key => {
				const room = roomList[key]

				const roomContacts = room.users.filter(
					user => user._id !== this.currentUserId
				)

				room.roomName =
					roomContacts.map(user => user.username).join(', ') || 'Myself'

				const roomAvatar =
					roomContacts.length === 1 && roomContacts[0].avatar
						? roomContacts[0].avatar
						: require('@/assets/logo.png')

				formattedRooms.push({
					...room,
					roomId: key,
					avatar: roomAvatar,
					index: room.lastUpdated.seconds,
					lastMessage: {
						content: 'Room created',
						timestamp: formatTimestamp(
							new Date(room.lastUpdated.seconds),
							room.lastUpdated
						)
					}
				})
			})

			this.rooms = this.rooms.concat(formattedRooms)
			formattedRooms.forEach(room => this.listenLastMessage(room))

			if (!this.rooms.length) {
				this.loadingRooms = false
				this.roomsLoadedCount = 0
			}

			this.listenUsersOnlineStatus(formattedRooms)
			this.listenRooms(query)
			// setTimeout(() => console.log('TOTAL', this.dbRequestCount), 2000)
		},

		listenLastMessage(room) {
			const listener = firestoreService.firestoreListener(
				firestoreService.lastMessageQuery(room.roomId),
				messages => {
					// this.incrementDbCounter('Listen Last Room Message', messages.size)
					messages.forEach(message => {
						const lastMessage = this.formatLastMessage(message.data())
						const roomIndex = this.rooms.findIndex(
							r => room.roomId === r.roomId
						)
						this.rooms[roomIndex].lastMessage = lastMessage
						this.rooms = [...this.rooms]
					})
					if (this.loadingLastMessageByRoom < this.rooms.length) {
						this.loadingLastMessageByRoom++

						if (this.loadingLastMessageByRoom === this.rooms.length) {
							this.loadingRooms = false
							this.roomsLoadedCount = this.rooms.length
						}
					}
				}
			)

			this.roomsListeners.push(listener)
		},

		formatLastMessage(message) {
			if (!message.timestamp) return

			let content = message.content
			if (message.files?.length) {
				const file = message.files[0]
				content = `${file.name}.${file.extension || file.type}`
			}

			return {
				...message,
				...{
					content,
					timestamp: formatTimestamp(
						new Date(message.timestamp.seconds * 1000),
						message.timestamp
					),
					distributed: true,
					seen: message.sender_id === this.currentUserId ? message.seen : null,
					new:
						message.sender_id !== this.currentUserId &&
						(!message.seen || !message.seen[this.currentUserId])
				}
			}
		},

		fetchMessages({ room, options = {} }) {
			this.$emit('show-demo-options', false)

			if (options.reset) {
				this.resetMessages()
				this.roomId = room.roomId
			}

			if (this.previousLastLoadedMessage && !this.lastLoadedMessage) {
				this.messagesLoaded = true
				return
			}

			this.selectedRoom = room.roomId

			firestoreService
				.getMessages(room.roomId, this.messagesPerPage, this.lastLoadedMessage)
				.then(messages => {
					// this.incrementDbCounter('Fetch Room Messages', messages.size)
					if (this.selectedRoom !== room.roomId) return

					if (messages.empty || messages.docs.length < this.messagesPerPage) {
						setTimeout(() => (this.messagesLoaded = true), 0)
					}

					if (options.reset) this.messages = []

					messages.forEach(message => {
						const formattedMessage = this.formatMessage(room, message)
						this.messages.unshift(formattedMessage)
					})

					if (this.lastLoadedMessage) {
						this.previousLastLoadedMessage = this.lastLoadedMessage
					}
					this.lastLoadedMessage = messages.docs[messages.docs.length - 1]

					const listener = firestoreService.firestoreListener(
						firestoreService.paginatedMessagesQuery(
							room.roomId,
							this.lastLoadedMessage,
							this.previousLastLoadedMessage
						),
						snapshots => {
							// this.incrementDbCounter('Listen Room Messages', snapshots.size)
							this.listenMessages(snapshots, room)
						}
					)
					this.listeners.push(listener)
				})
		},

		listenMessages(messages, room) {
			messages.forEach(message => {
				const formattedMessage = this.formatMessage(room, message)
				const messageIndex = this.messages.findIndex(m => m._id === message.id)

				if (messageIndex === -1) {
					this.messages = this.messages.concat([formattedMessage])
				} else {
					this.messages[messageIndex] = formattedMessage
					this.messages = [...this.messages]
				}

				this.markMessagesSeen(room, message)
			})
		},

		markMessagesSeen(room, message) {
			if (
				message.data().sender_id !== this.currentUserId &&
				(!message.data().seen || !message.data().seen[this.currentUserId])
			) {
				firestoreService.updateMessage(room.roomId, message.id, {
					[`seen.${this.currentUserId}`]: new Date()
				})
			}
		},

		formatMessage(room, message) {
			const senderUser = room.users.find(
				user => message.data().sender_id === user._id
			)

			const { timestamp } = message.data()

			const formattedMessage = {
				...message.data(),
				...{
					senderId: message.data().sender_id,
					_id: message.id,
					seconds: timestamp.seconds,
					timestamp: parseTimestamp(timestamp, 'HH:mm'),
					date: parseTimestamp(timestamp, 'DD MMMM YYYY'),
					username: senderUser ? senderUser.username : null,
					// avatar: senderUser ? senderUser.avatar : null,
					distributed: true
				}
			}

			if (message.data().replyMessage) {
				formattedMessage.replyMessage = {
					...message.data().replyMessage,
					...{
						senderId: message.data().replyMessage.sender_id
					}
				}
			}

			return formattedMessage
		},

		async sendMessage({ content, roomId, files, replyMessage }) {
			const message = {
				sender_id: this.currentUserId,
				content,
				timestamp: new Date()
			}

			if (files) {
				message.files = this.formattedFiles(files)
			}

			if (replyMessage) {
				message.replyMessage = {
					_id: replyMessage._id,
					content: replyMessage.content,
					sender_id: replyMessage.senderId
				}

				if (replyMessage.files) {
					message.replyMessage.files = replyMessage.files
				}
			}

			const { id } = await firestoreService.addMessage(roomId, message)

			if (files) {
				for (let index = 0; index < files.length; index++) {
					await this.uploadFile({ file: files[index], messageId: id, roomId })
				}
			}

			firestoreService.updateRoom(roomId, { lastUpdated: new Date() })
		},

		async editMessage({ messageId, newContent, roomId, files }) {
			const newMessage = { edited: new Date() }
			newMessage.content = newContent

			if (files) {
				newMessage.files = this.formattedFiles(files)
			} else {
				newMessage.files = firestoreService.deleteDbField
			}

			await firestoreService.updateMessage(roomId, messageId, newMessage)

			if (files) {
				for (let index = 0; index < files.length; index++) {
					if (files[index]?.blob) {
						await this.uploadFile({ file: files[index], messageId, roomId })
					}
				}
			}
		},

		async deleteMessage({ message, roomId }) {
			await firestoreService.updateMessage(roomId, message._id, {
				deleted: new Date()
			})

			const { files } = message

			if (files) {
				files.forEach(file => {
					storageService.deleteFile(this.currentUserId, message._id, file)
				})
			}
		},

		async uploadFile({ file, messageId, roomId }) {
			let type = file.extension || file.type
			if (type === 'svg' || type === 'pdf') {
				type = file.type
			}

			const uploadTask = storageService.uploadFileTask(
				this.currentUserId,
				messageId,
				file,
				type
			)

			uploadTask.on(
				'state_changed',
				snap => {
					const progress = Math.round(
						(snap.bytesTransferred / snap.totalBytes) * 100
					)
					this.updateFileProgress(messageId, file.localUrl, progress)
				},
				_error => {},
				async () => {
					const url = await storageService.getFileDownloadUrl(
						uploadTask.snapshot.ref
					)

					const messageDoc = await firestoreService.getMessage(
						roomId,
						messageId
					)

					const files = messageDoc.data().files

					files.forEach(f => {
						if (f.url === file.localUrl) {
							f.url = url
						}
					})

					firestoreService.updateMessage(roomId, messageId, { files })
				}
			)
		},

		updateFileProgress(messageId, fileUrl, progress) {
			const message = this.messages.find(message => message._id === messageId)

			if (!message || !message.files) return

			message.files.find(file => file.url === fileUrl).progress = progress
			this.messages = [...this.messages]
		},

		formattedFiles(files) {
			const formattedFiles = []

			files.forEach(file => {
				const messageFile = {
					name: file.name,
					size: file.size,
					type: file.type,
					extension: file.extension || file.type,
					url: file.url || file.localUrl
				}

				if (file.audio) {
					messageFile.audio = true
					messageFile.duration = file.duration
				}

				formattedFiles.push(messageFile)
			})

			return formattedFiles
		},

		openFile({ file }) {
			window.open(file.file.url, '_blank')
		},

		async openUserTag({ user }) {
			let roomId

			this.rooms.forEach(room => {
				if (room.users.length === 2) {
					const userId1 = room.users[0]._id
					const userId2 = room.users[1]._id
					if (
						(userId1 === user._id || userId1 === this.currentUserId) &&
						(userId2 === user._id || userId2 === this.currentUserId)
					) {
						roomId = room.roomId
					}
				}
			})

			if (roomId) {
				this.roomId = roomId
				return
			}

			const query1 = await firestoreService.getUserRooms(
				this.currentUserId,
				user._id
			)

			if (!query1.empty) {
				return this.loadRoom(query1)
			}

			const query2 = await firestoreService.getUserRooms(
				user._id,
				this.currentUserId
			)

			if (!query2.empty) {
				return this.loadRoom(query2)
			}

			const room = await firestoreService.addRoom({
				users: [user._id, this.currentUserId],
				lastUpdated: new Date()
			})

			this.roomId = room.id
			this.fetchRooms()
		},

		async loadRoom(query) {
			query.forEach(async room => {
				if (this.loadingRooms) return
				await firestoreService.updateRoom(room.id, { lastUpdated: new Date() })
				this.roomId = room.id
				this.fetchRooms()
			})
		},

		menuActionHandler({ action, roomId }) {
			switch (action.name) {
				case 'inviteUser':
					return this.inviteUser(roomId)
				case 'removeUser':
					return this.removeUser(roomId)
				case 'deleteRoom':
					return this.deleteRoom(roomId)
			}
		},

		async sendMessageReaction({ reaction, remove, messageId, roomId }) {
			firestoreService.updateMessageReactions(
				roomId,
				messageId,
				this.currentUserId,
				reaction.unicode,
				remove ? 'remove' : 'add'
			)
		},

		typingMessage({ message, roomId }) {
			if (roomId) {
				if (message?.length > 1) {
					this.typingMessageCache = message
					return
				}

				if (message?.length === 1 && this.typingMessageCache) {
					this.typingMessageCache = message
					return
				}

				this.typingMessageCache = message

				firestoreService.updateRoomTypingUsers(
					roomId,
					this.currentUserId,
					message ? 'add' : 'remove'
				)
			}
		},

		async listenRooms(query) {
			const listener = firestoreService.firestoreListener(query, rooms => {
				// this.incrementDbCounter('Listen Rooms Typing Users', rooms.size)
				rooms.forEach(room => {
					const foundRoom = this.rooms.find(r => r.roomId === room.id)
					if (foundRoom) {
						foundRoom.typingUsers = room.data().typingUsers
						foundRoom.index = room.data().lastUpdated.seconds
					}
				})
			})
			this.roomsListeners.push(listener)
		},

		listenUsersOnlineStatus(rooms) {
			rooms.forEach(room => {
				room.users.forEach(user => {
					const listener = firebaseService.firebaseListener(
						firebaseService.userStatusRef(user._id),
						snapshot => {
							if (!snapshot || !snapshot.val()) return

							const lastChanged = formatTimestamp(
								new Date(snapshot.val().lastChanged),
								new Date(snapshot.val().lastChanged)
							)

							user.status = { ...snapshot.val(), lastChanged }

							const roomIndex = this.rooms.findIndex(
								r => room.roomId === r.roomId
							)

							this.rooms[roomIndex] = room
							this.rooms = [...this.rooms]
						}
					)
					this.roomsListeners.push(listener)
				})
			})
		},

		addRoom() {
			this.resetForms()
			this.addNewRoom = true
		},

		async createRoom() {
			this.disableForm = true

			const { id } = await firestoreService.addUser({
				username: this.addRoomUsername
			})
			await firestoreService.updateUser(id, { _id: id })

			await firestoreService.addRoom({
				users: [id, this.currentUserId],
				lastUpdated: new Date()
			})

			this.addNewRoom = false
			this.addRoomUsername = ''
			this.fetchRooms()
		},

		inviteUser(roomId) {
			this.resetForms()
			this.inviteRoomId = roomId
		},

		async addRoomUser() {
			this.disableForm = true

			const { id } = await firestoreService.addUser({
				username: this.invitedUsername
			})
			await firestoreService.updateUser(id, { _id: id })

			await firestoreService.addRoomUser(this.inviteRoomId, id)

			this.inviteRoomId = null
			this.invitedUsername = ''
			this.fetchRooms()
		},

		removeUser(roomId) {
			this.resetForms()
			this.removeRoomId = roomId
			this.removeUsers = this.rooms.find(room => room.roomId === roomId).users
		},

		async deleteRoomUser() {
			this.disableForm = true

			await firestoreService.removeRoomUser(
				this.removeRoomId,
				this.removeUserId
			)

			this.removeRoomId = null
			this.removeUserId = ''
			this.fetchRooms()
		},

		async deleteRoom(roomId) {
			const room = this.rooms.find(r => r.roomId === roomId)
			if (
				room.users.find(user => user._id === 'SGmFnBZB4xxMv9V4CVlW') ||
				room.users.find(user => user._id === '6jMsIXUrBHBj7o2cRlau')
			) {
				return alert('Nope, for demo purposes you cannot delete this room')
			}

			firestoreService.getMessages(roomId).then(messages => {
				messages.forEach(message => {
					firestoreService.deleteMessage(roomId, message.id)
					if (message.data().files) {
						message.data().files.forEach(file => {
							storageService.deleteFile(this.currentUserId, message.id, file)
						})
					}
				})
			})

			await firestoreService.deleteRoom(roomId)

			this.fetchRooms()
		},

		resetForms() {
			this.disableForm = false
			this.addNewRoom = null
			this.addRoomUsername = ''
			this.inviteRoomId = null
			this.invitedUsername = ''
			this.removeRoomId = null
			this.removeUserId = ''
		}

		// ,incrementDbCounter(type, size) {
		// 	size = size || 1
		// 	this.dbRequestCount += size
		// 	console.log(type, size)
		// }
	}
}
</script>

<style lang="scss" scoped>
.window-container {
	width: 100%;
}

.window-mobile {
	form {
		padding: 0 10px 10px;
	}
}

form {
	padding-bottom: 20px;
}

input {
	padding: 5px;
	width: 140px;
	height: 21px;
	border-radius: 4px;
	border: 1px solid #d2d6da;
	outline: none;
	font-size: 14px;
	vertical-align: middle;

	&::placeholder {
		color: #9ca6af;
	}
}

button {
	background: #1976d2;
	color: #fff;
	outline: none;
	cursor: pointer;
	border-radius: 4px;
	padding: 8px 12px;
	margin-left: 10px;
	border: none;
	font-size: 14px;
	transition: 0.3s;
	vertical-align: middle;

	&:hover {
		opacity: 0.8;
	}

	&:active {
		opacity: 0.6;
	}

	&:disabled {
		cursor: initial;
		background: #c6c9cc;
		opacity: 0.6;
	}
}

.button-cancel {
	color: #a8aeb3;
	background: none;
	margin-left: 5px;
}

select {
	vertical-align: middle;
	height: 33px;
	width: 152px;
	font-size: 13px;
	margin: 0 !important;
}
</style>
